using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Web.Mvc;
using System.Web.Mvc.Html;
using MvcContrib.UI.Grid;
using NavigationBuilder;
using Webbness.Mvc.Models;

namespace Webbness.Mvc.Extensions
{
    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="TModel">The type of the model.</typeparam>
    public class ActionLinks<TModel> where TModel : Record
    {
        private static Dictionary<string, AuthorizeAttribute[]> _authorizeAttributeCache;
        private readonly ColumnBuilder<TModel> _builder;
        private readonly string _colTitle;
        private readonly HtmlHelper _helper;
        private List<LinkItem> _linkItems;

        /// <summary>
        /// Initializes a new instance of the <see cref="ActionLinks&lt;TModel&gt;"/> class.
        /// </summary>
        /// <param name="column">The column.</param>
        /// <param name="helper">The helper.</param>
        /// <param name="title">The title.</param>
        public ActionLinks(ColumnBuilder<TModel> column, HtmlHelper helper, string title)
        {
            _builder = column;
            _helper = helper;
            _colTitle = title;
            _authorizeAttributeCache = new Dictionary<string, AuthorizeAttribute[]>();
            _linkItems = new List<LinkItem>();
        }

        /// <summary>
        /// Adds the actions.
        /// </summary>
        /// <typeparam name="TController">The type of the controller.</typeparam>
        /// <param name="items">The items.</param>
        /// <returns></returns>
        public ActionLinks<TModel> AddActions<TController>(params LinkToItem<TController>[] items) where TController : class
        {
            foreach (var linkToItem in items)
            {
                var callExpression = linkToItem.MenuAction.Body as MethodCallExpression;

                if (callExpression == null)
                    throw new InvalidOperationException("Expression must be a method call");

                if (linkToItem.Secure)
                {
                    AuthorizeAttribute[] attributes = GetAuthorizeAttributes(callExpression);

                    if (CanAddItem(attributes))
                    {
                        _linkItems.Add(new LinkItem {Title = linkToItem.Title, Secure = true, MethodExpression = callExpression, RouteValues = linkToItem.RouteValues});
                    }
                }
                else
                {
                    _linkItems.Add(new LinkItem { Title = linkToItem.Title, Secure = false, MethodExpression = callExpression, RouteValues = linkToItem.RouteValues });
                }
            }

            _builder.For(x => buildActions(x)).DoNotEncode().Named(_colTitle).Sortable(false);

            return this;
        }

        public override string ToString()
        {
            return buildActions();
        }

        private string buildActions(TModel model)
        {
            var sb = new StringBuilder();
            sb.Append("&nbsp;");
            foreach (LinkItem linkItem in _linkItems)
            {
                if (linkItem.RouteValues.Count == 0)
                    sb.Append(_helper.ActionLink(linkItem.Title, linkItem.MethodExpression.Method.Name, new {id = model.Id})).Append("&nbsp;");
                else
                    sb.Append(_helper.ActionLink(linkItem.Title, linkItem.MethodExpression.Method.Name, linkItem.RouteValues)).Append("&nbsp;");
            }
            return sb.ToString();
        }

        private string buildActions()
        {
            var sb = new StringBuilder();
            sb.Append("&nbsp;");
            foreach (LinkItem linkItem in _linkItems)
            {
                sb.Append(_helper.ActionLink(linkItem.Title, linkItem.MethodExpression.Method.Name, linkItem.RouteValues)).Append("&nbsp;");
            }
            return sb.ToString();
        }

        public ColumnBuilder<TModel> Render()
        {
            return _builder;
        }

        private bool CanAddItem(IEnumerable<AuthorizeAttribute> attributes)
        {
            foreach (AuthorizeAttribute attribute in attributes)
            {
                if (attribute != null && !attribute.Authorized(_helper.ViewContext.Controller.ControllerContext))
                    return false;
            }
            return true;
        }

        private static AuthorizeAttribute[] GetAuthorizeAttributes(MethodCallExpression methodInfo)
        {
            if (methodInfo == null)
            {
                throw new ArgumentNullException("methodInfo");
            }

            if (_authorizeAttributeCache.ContainsKey(methodInfo.Method.ToString()))
            {
                return _authorizeAttributeCache[methodInfo.Method.ToString()];
            }

            MethodInfo concreteMethodInfo = null;

            foreach (MethodInfo info in methodInfo.Object.Type.GetMethods())
            {
                if (info.ToString().Equals(methodInfo.Method.ToString()))
                {
                    concreteMethodInfo = info;
                    break;
                }
            }

            var typeFilters = (AuthorizeAttribute[]) methodInfo.Object.Type.GetCustomAttributes(typeof (AuthorizeAttribute), true /* inherit */);
            AuthorizeAttribute[] methodFilters;

            if (concreteMethodInfo == null)
            {
                methodFilters = (AuthorizeAttribute[]) methodInfo.Method.GetCustomAttributes(typeof (AuthorizeAttribute), true /* inherit */);
            }
            else
            {
                methodFilters = (AuthorizeAttribute[]) concreteMethodInfo.GetCustomAttributes(typeof (AuthorizeAttribute), true /* inherit */);
            }
            AuthorizeAttribute[] x = typeFilters.Concat(methodFilters).OrderBy(attr => attr.Order).ToArray();
            _authorizeAttributeCache[methodInfo.Method.ToString()] = x;
            return x;
        }
    }
}